﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Management;
using System.Management.Automation;
using NUnit.Framework;
using System.Text.RegularExpressions;
using System.Management.Automation.Runspaces;
using System.IO;
using System.Collections.ObjectModel;
using Obra.Common;
using System.Reflection;
using System.Globalization;

namespace Obra.Test.SmugMug.PowerShell
{
    [TestFixture]
    public class PowerShellTester
    {
        private readonly Uri uri = new Uri(Messages.TestUrl);
        private HttpServerMatchedContent httpServer;
        private Runspace runspace;

        [SetUp]
        public void SetUp()
        {
            this.httpServer = new HttpServerMatchedContent(this.uri);
            this.httpServer.DefaultMatchContent =
                new MatchedContentFile(".*", @"..\..\..\SmugMug\sample\InvalidMethod.xml");

            this.httpServer.MatchedContents.Add(
                new MatchedContentFile(@"smugmug\.login\.withPassword&", @"..\..\SmugMug\sample\SmugMugLoginWithPassword.xml"));
            this.httpServer.MatchedContents.Add(
                new MatchedContentFile(@"smugmug\.albums\.get&", @"..\..\SmugMug\sample\SmugMugAlbumsGet2.xml"));
            this.httpServer.MatchedContents.Add(
                new MatchedContentFile(@"smugmug\.images\.get&", @"..\..\SmugMug\sample\SmugMugImagesGet.xml"));
            this.httpServer.MatchedContents.Add(
                new MatchedContentFile(@"smugmug\.images\.getEXIF&", @"..\..\SmugMug\sample\SmugMugImageExif.xml"));
            this.httpServer.MatchedContents.Add(
                new MatchedContentFile(@"smugmug\.logout&", @"..\..\SmugMug\sample\SmugMugLogout.xml"));

            this.httpServer.Start();

            RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();
            PowerShellTestHelpers.RegisterSnapIn(
                "SmugMug",
                Path.GetFullPath("Obra.PowerShell.SmugMug.dll"),
                new List<string>(),
                new List<string>() { "SmugMug.format.ps1xml" },
                runspaceConfiguration);

            this.runspace = RunspaceFactory.CreateRunspace(
                runspaceConfiguration);

            this.runspace.Open();

            PSCredential credential = new PSCredential(
                "joe",
                Credential.StringToSecureString("secret"));

            this.runspace.SessionStateProxy.SetVariable(
                "credential",
                credential);
            var results = this.runspace.CreatePipeline("$credential").Invoke();

            this.runspace.CreatePipeline(String.Format(
                @"new-psdrive -name smugmug -root smugmug:\ -psprovider smugmug -credential $credential -Uri {0}", 
                Messages.TestUrl)).Invoke();

            this.runspace.CreatePipeline(
                @"update-formatdata ..\..\..\Obra.PowerShell.SmugMug\SmugMug.format.ps1xml").Invoke();
            this.runspace.CreatePipeline(
                @"set-location smugmug:").Invoke();
        }

        [TearDown]
        public void TearDown()
        {
            this.runspace.CreatePipeline(
                @"remove-psdrive smugmug").Invoke();
            this.runspace.CreatePipeline(
                @"remove-pssnapin smugmug").Invoke();

            this.runspace.Close();
            this.httpServer.Stop();
        }

        Collection<PSObject> Invoke(string cmd)
        {
            return this.runspace.CreatePipeline(cmd).Invoke();
        }

        /// <summary>
        /// Return true if the CmdletInvocationException fired.  This
        /// exception fires if PowerShell throws an exception due to 
        /// invocation of an invalid command.
        /// </summary>
        bool InvokeExpectedCmdletInvocationException(string cmd)
        {
            try
            {
                this.Invoke(cmd);
            }
            catch (CmdletInvocationException)
            {
                return true;
            }
            catch (Exception e)
            {
                Assert.Fail(e.ToString());
            }

            return false;
        }

        /// <summary>
        /// Return true if the CmdletProviderInvocationException fired.  This
        /// exception fires if one of my exception fires.
        /// </summary>
        bool InvokeExpectedCmdletProviderInvocationException(string cmd)
        {
            try
            {
                this.Invoke(cmd);
            }
            catch (CmdletProviderInvocationException)
            {
                return true;
            }
            catch (Exception e)
            {
                Assert.Fail(e.ToString());
            }

            return false;
        }

        private PathInfo AssertPath(string path)
        {
            PathInfo pathInfo = this.runspace.CreatePipeline("get-location").Invoke()[0].BaseObject as
                System.Management.Automation.PathInfo;

            Assert.IsNotNull(pathInfo);
            Assert.AreEqual(path, pathInfo.Path);

            return pathInfo;
        }

        [Test]
        public void CopyItem()
        {
            this.InvokeExpectedCmdletProviderInvocationException("Copy-Item Album1");
        }

        [Test]
        public void GetChildItem()
        {
            PathInfo pathInfo = this.AssertPath(@"smugmug:\");
            Assert.AreEqual("smugmug", pathInfo.Drive.Name);

            var albums = this.Invoke("dir");
            Assert.AreEqual(2, albums.Count);

            int countA = 1;
            foreach (var itemA in albums)
            {
                var album = itemA.BaseObject as Obra.SmugMug.Api.Album;
                Assert.IsNotNull(album);
                Assert.AreEqual(typeof(Obra.SmugMug.Api.Album).FullName, itemA.TypeNames[0]);

                string name = String.Format("Album{0}", countA++);
                Assert.AreEqual(name, album.Name);

                this.Invoke(String.Format("cd \\{0}", album.Name));
                this.AssertPath(String.Format(@"smugmug:\{0}", album.Name));

                var images = this.Invoke("dir");
                Assert.AreEqual(6, images.Count);

                int countI = 1;
                foreach (var itemI in images)
                {
                    var image = itemI.BaseObject as Obra.SmugMug.Api.Image;
                    Assert.IsNotNull(image);
                    Assert.AreEqual(typeof(Obra.SmugMug.Api.Image).FullName, itemI.TypeNames[0]);

                    string nameI = String.Format("image-{0}.jpg", countI++);
                    Assert.AreEqual(nameI, image.Name);
                }
            }
        }

        [Test]
        public void GetItem()
        {
            PathInfo pathInfo = this.AssertPath(@"smugmug:\");
            Assert.AreEqual("smugmug", pathInfo.Drive.Name);

            var album = this.Invoke("Get-Item Album1").First().BaseObject as Obra.SmugMug.Api.Album;
            var image = this.Invoke("Get-Item Album1\\image-1.jpg").First().BaseObject as Obra.SmugMug.Api.Image;

            Assert.IsNotNull(album);
            Assert.AreEqual("Album1", album.Name);
            Assert.AreEqual(1000, album.Id);

            Assert.IsNotNull(image);
            Assert.AreEqual("image-1.jpg", image.Name);
            Assert.AreEqual(1001, image.Id);
        }

        [Test]
        public void GetItemProperty()
        {
        }

        [Test]
        public void InvokeDefaultAction()
        {
        }

        [Test]
        public void MoveItem()
        {
            var matchMoveItem = new MatchedContentFile(
                @"smugmug\.images\.changeSettings&", 
                @"..\..\SmugMug\sample\SmugMugImagesDeleteSuccess.xml");

            this.httpServer.MatchedContents.Add(
                matchMoveItem);

            this.Invoke("Move-Item Album1\\image-3.jpg Album2");

            Assert.AreEqual(1, matchMoveItem.Requests.Count);
            StringAssert.Contains(
                "ImageID=1003",
                matchMoveItem.Requests[0].RawUrl);
            StringAssert.Contains(
                "AlbumID=1001",
                matchMoveItem.Requests[0].RawUrl);
                
            this.InvokeExpectedCmdletProviderInvocationException(
                "Move-Item Album1\\image-1.jpg Album1\\image-11.jpg");

            this.InvokeExpectedCmdletProviderInvocationException("Move-Item Album1 Album3");
            this.InvokeExpectedCmdletProviderInvocationException("Move-Item Album1 Album1");
            this.InvokeExpectedCmdletProviderInvocationException("Move-Item Album1 Album2");

            this.Invoke("cd Album1");
            this.InvokeExpectedCmdletProviderInvocationException(
                "Move-Item image-2.jpg image-12.jpg");
            this.InvokeExpectedCmdletProviderInvocationException(
                "Move-Item image-4.jpg image-4.jpg");
            this.InvokeExpectedCmdletProviderInvocationException(
                "Move-Item image-4.jpg image-5.jpg");
        }

        [Test]
        public void NewItem()
        {
            this.InvokeExpectedCmdletProviderInvocationException("New-Item foo");
        }

        [Test]
        public void RemoveItem()
        {
            var matchImagesDelete = new MatchedContentFile(
                @"smugmug\.images\.delete&", 
                @"..\..\SmugMug\sample\SmugMugImagesDeleteSuccess.xml");

            var matchAlbumsDelete =
                new MatchedContentFile(@"smugmug\.albums\.delete&", @"..\..\SmugMug\sample\SmugMugAlbumsDeleteSuccess.xml");

            this.httpServer.MatchedContents.Add(
                matchImagesDelete);

            this.httpServer.MatchedContents.Add(
                matchAlbumsDelete);

            this.Invoke("Remove-Item Album1\\image-1.jpg");
            this.Invoke("Remove-Item -Recurse Album1");

            this.InvokeExpectedCmdletInvocationException("Remove-Item Album1");
            this.InvokeExpectedCmdletProviderInvocationException("Remove-Item Album1\\image-100.jpg");

            Assert.AreEqual(1, matchImagesDelete.Requests.Count);
            Assert.AreEqual(1, matchAlbumsDelete.Requests.Count);

            StringAssert.Contains(
                "ImageID=1001",
                matchImagesDelete.Requests[0].RawUrl);

            StringAssert.Contains(
                "AlbumID=1000",
                matchAlbumsDelete.Requests[0].RawUrl);

        }

        [Test]
        public void RenameItem()
        {
            var matchAlbumsChangeSettings =
                new MatchedContentFile(@"smugmug\.albums\.changeSettings&", @"..\..\SmugMug\sample\SmugMugAlbumsChangeSettingsSuccess.xml");

            this.httpServer.MatchedContents.Add(
                matchAlbumsChangeSettings);

            this.Invoke("Rename-Item Album1 Album3");

            Assert.AreEqual(1, matchAlbumsChangeSettings.Requests.Count);

            StringAssert.Contains(
                "Title=Album3",
                matchAlbumsChangeSettings.Requests[0].RawUrl);

            StringAssert.Contains(
                "AlbumID=1000",
                matchAlbumsChangeSettings.Requests[0].RawUrl);

            this.InvokeExpectedCmdletProviderInvocationException("Rename-Item Album1 Album1");
            this.InvokeExpectedCmdletProviderInvocationException("Rename-Item Album1 Album2");
            this.InvokeExpectedCmdletProviderInvocationException("Rename-Item Album1\\image-1.jpg Album1\\image-11.jpg");
        }

        [Test]
        public void SetItem()
        {
            this.InvokeExpectedCmdletProviderInvocationException("Set-Item foo");
        }

        [Test]
        public void SetItemProperty()
        {
        }

        [Test]
        public void TestPath()
        {
            Assert.IsTrue((bool)this.Invoke("Test-Path Album1").First().BaseObject);
            Assert.IsTrue((bool)this.Invoke("Test-Path Album1\\image-1.jpg").First().BaseObject);

            Assert.IsFalse((bool)this.Invoke("Test-Path Does\\Not\\Exist").First().BaseObject);
        }
    }
}
